// Copyright 2013 The Go Circuit Project
// Use of this source code is governed by the license for
// The Go Circuit Project, found in the LICENSE file.
//
// Authors:
//   2013 Petar Maymounkov <p@gocircuit.org>

package ns

import (
	"fmt"
	"reflect"
	"sync"
	"time"
	"unsafe"

	"circuit/kit/rh"
)

// InterfaceDir
type InterfaceDir interface{}

var typeOfInterfaceDir = reflect.TypeOf(new(InterfaceDir)).Elem()

// interfaceFID
type interfaceFID struct {
	rh.ReadOnlyFID
	//
	name     string
	mtime    time.Time     // modify = create time
	recv     reflect.Value // receiver
	proc     []*interfaceProc
	__       sync.Mutex
	__atime  time.Time
	__parent rh.FID
}

func newInterfaceFID(name string, recv InterfaceDir) *interfaceFID {
	now := time.Now()
	d := &interfaceFID{
		name:    name,
		mtime:   now,
		recv:    reflect.ValueOf(recv),
		__atime: now,
	}
	t := d.recv.Type()
	for i := 0; i < t.NumMethod(); i++ {
		p := newInterfaceProc(i, t, d.recv, now)
		if p != nil {
			d.proc = append(d.proc, p)
		}
	}
	return d
}

func (fid *interfaceFID) String() string {
	return fmt.Sprintf("%s:(%02x)", fid.name, uint64(uintptr(unsafe.Pointer(fid)))&0xff)
}

func (fid *interfaceFID) attach(parent rh.FID) {
	fid.__.Lock()
	defer fid.__.Unlock()
	fid.__parent = parent
}

func (fid *interfaceFID) Q() rh.Q {
	return rh.Q{
		ID:  valueHash(fid.recv),
		Ver: 1,
	}
}

func (fid *interfaceFID) Open(flag rh.Flag, _ rh.Intr) error {
	return nil
}

func (fid *interfaceFID) Clunk() {}

func (fid *interfaceFID) Stat() (*rh.Dir, error) {
	fid.__.Lock()
	defer fid.__.Unlock()
	return &rh.Dir{
		Q:      fid.Q(),
		Mode:   rh.Mode{Attr: rh.ModeDir},
		Perm:   0555, // r-xr-xr-x
		Atime:  fid.__atime,
		Mtime:  fid.mtime,
		Length: int64(len(fid.proc)),
		Name:   fid.name,
		Uid:    uid(),
		Gid:    gid(),
	}, nil
}

func (fid *interfaceFID) Walk(wname []string) (rh.FID, error) {
	if len(wname) == 0 {
		return fid, nil
	}
	for _, proc := range fid.proc {
		if proc.FName == wname[0] {
			return proc.walk(), nil
		}
	}
	return nil, rh.ErrNotExist
}

func (fid *interfaceFID) Read(offset int64, count int, _ rh.Intr) (rh.Chunk, error) {
	//
	var off = int(offset)
	if off >= len(fid.proc) {
		return nil, nil
	}
	n := len(fid.proc) - off
	if count > 0 {
		n = min(n, count)
	}
	r := make(rh.DirChunk, 0, n)
	for i := 0; i < n; i++ {
		if x, err := fid.proc[off+i].Stat(); err == nil { // If stat successfully read
			if !x.Mode.IsHidden { // And not an empty directory
				r = append(r, x) // Include fid in listing
			}
		}
	}
	return r, nil
}
